from fastapi import FastAPI,Response,Request
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, JSONResponse
import uvicorn
import os
from pydantic import BaseModel
import json
import pymysql
import uuid
from datetime import datetime
from pathlib import Path

mysql_config = {
    "host": "127.0.0.1",    # 数据库服务器地址
    "port": 3306,           # 端口，默认 3306
    "user": "root",          # 用户名
    "password": "JJDJRnb666@+",  # 密码
    "database": "news_sys",  # 数据库名
    "charset": "utf8mb4",    # 字符集
    "cursorclass": pymysql.cursors.DictCursor #查询返回结果为字典
}

static_path = "./static/" #存放静态资源的路径
news_path = "/news/" #存放新闻的路径

#处理注册请求的方法
def handle_register(in_data) -> JSONResponse:
    #1.提取数据
    print(in_data)
    name = in_data["name"] #用户名
    passwd = in_data["passwd"] #密码
    result = in_data["result"] #验证结果
    exp = in_data["exp"] #验证表达式
    #2.验证用户输入的验证码是否正确
    correct_result = 0
        #2.1先计算结果
    exp = exp.split(' ')
    if exp[1] == '+':
        correct_result = int(exp[0]) + int(exp[2])
    if exp[1] == '-':
        correct_result = int(exp[0]) - int(exp[2])
    if exp[1] == '*':
        correct_result = int(exp[0]) * int(exp[2])
        #2.2再将正确结果和用户输入的验证码对比
    if correct_result != int(result):
        print(f"注册失败，因为验证码不匹配，{result}!={correct_result}")
        return JSONResponse(
            status_code = 400,
            content = {"message": "验证码错误"}
        )
    #3. ****root**** 这个子串只能在管理员的用户名中出现
    if "****root****" in name:
        #用户如果想注册管理员账号，就提示他该账号已存在
        print(f"注册失败，因为视图注册管理员账号，{result}!={correct_result}")
        return JSONResponse(
            status_code = 400,
            content = {"message": "该用户已存在"}
        )
    try:
        with pymysql.connect(**mysql_config) as conn:
            with conn.cursor() as cursor:
                #4.从数据库中验证注册的用户是否已存在
                sql = f"select * from user where name = '{name}';"
                cursor.execute(sql)
                data = cursor.fetchall()
                if len(data) != 0:
                    #用户已存在
                    return JSONResponse(
                        status_code = 400,
                        content = {"message": "该用户已存在"}
                    )
                #5.确定是新用户，把账号密码保存到数据库中
                sql = f"insert into user values ('{name}','{passwd}');"
                cursor.execute(sql)
                conn.commit()
                return JSONResponse(
                    status_code = 200,
                    content = {"message": "注册成功"}
                )
    except Exception as e:
        print(f"注册失败，因为{e}")
        return JSONResponse(
            status_code = 400,
            content = {"message": "注册失败"}
        )

#2.处理登录请求的方法
def handle_login(in_data) -> JSONResponse:
    #1.提取数据
    name = in_data["name"]
    passwd = in_data["passwd"]
    try:
        with pymysql.connect(**mysql_config) as conn:
            with conn.cursor() as cursor:
                #1.在数据库中确认用户是否存在
                sql = f"select * from user where name = '{name}';"
                cursor.execute(sql)
                data = cursor.fetchall()
                if len(data) == 0:
                    return JSONResponse(
                        status_code = 400,
                        content = {"message": "用户不存在"}
                    )
                if len(data) > 1:
                    print(f"{name}用户数量={len(data)}")
                    return JSONResponse(
                        status_code = 400,
                        content = {"message": "异常用户数量，请联系管理员"}
                    )
                #2.验证账号与密码是否匹配
                if data[0]["passwd"] != passwd:
                    return JSONResponse(
                        status_code = 400,
                        content = {"message": "密码错误"}
                    )
                #3.验证通过，根据登陆者是普通用户还是管理员，设置不同的响应
                if "****root****" in name:
                    #管理员
                    return JSONResponse(
                        status_code=200,
                        content={"redirect_url": "/root_home.html"}
                    )
                else:
                    #普通用户
                    return JSONResponse(
                        status_code=200,
                        content={"redirect_url": "/home.html"}
                    )
    except Exception as e:
        print(f"登录失败，因为{e}")
        return JSONResponse(
            status_code = 400,
            content = {"message": "登录失败"}
        )

#处理获取所有新闻的请求
def handle_get_news() -> JSONResponse:
    print("请求获取所有新闻")
    try:
        with pymysql.connect(**mysql_config) as conn:
            with conn.cursor() as cursor:
                #1.从数据库中获取所有新闻
                sql = "select id,title,class from news;"
                cursor.execute(sql)
                news_dict = {}
                data = cursor.fetchall()
                for x in data:
                    news_dict[f"news{x['id']}"] ={
                        "news_id": x["id"],
                        "news_title": x["title"],
                        "news_class": x["class"]
                    }
                return JSONResponse(
                    status_code=200,
                    content=news_dict
                )
    except Exception as e:
        print(f"获取所有新闻失败,{e}")
        return JSONResponse(
            status_code = 400,
            content = {"message": "获取所有新闻失败"}
        )

#处理获取所有新闻型的请求
def handle_get_class() -> JSONResponse:
    print("请求获取所有新闻类型")
    try:
        with pymysql.connect(**mysql_config) as conn:
            with conn.cursor() as cursor:
                #1.从数据库中获取所有新闻
                sql = "select * from class where class_name != '其它';"
                cursor.execute(sql)
                data = cursor.fetchall()
                arr = []
                for x in data:
                    arr.append(x["class_name"])
                return JSONResponse(
                    status_code=200,
                    content={"class": arr}
                )
    except Exception as e:
        print(f"获取所有新闻类型失败,{e}")
        return JSONResponse(
            status_code = 400,
            content = {"message": "获取所有新闻类型失败"}
        )

#处理新增分类的请求
def handle_add_class(in_data) -> JSONResponse:
    #1.提取要新增的分类
    class_name = in_data["class_name"]
    if len(class_name) <= 0:
        return JSONResponse(
            status_code = 400,
            content = {"message": "数据不完整"}
        )
    try:
        with pymysql.connect(**mysql_config) as conn:
                with conn.cursor() as cursor:
                    #2.确认该分类是否已存在
                    sql = f"select * from class where class_name = '{class_name}';"
                    cursor.execute(sql)
                    data = cursor.fetchall()
                    if len(data) != 0:
                        #该分类已存在
                        return
                    #3.将该分类加入数据库
                    sql = f"insert into class values ('{class_name}');"
                    cursor.execute(sql)
                    conn.commit()
                    return
    except Exception as e:
        print(f"新增分类失败：{e}")
        return JSONResponse(
            status_code = 400,
            content = {"message": "新增分类失败"}
        )
#处理删除分类的请求
def handle_erase_class(in_data) -> JSONResponse:
    #1.提取要删除的分类
    class_name = in_data["class_name"]
    if len(class_name) <= 0:
        return JSONResponse(
            status_code = 400,
            content = {"message": "数据不完整"}
        )
    try:
        with pymysql.connect(**mysql_config) as conn:
                with conn.cursor() as cursor:
                    #2.确认该分类是否不存在
                    sql = f"select * from class where class_name = '{class_name}';"
                    cursor.execute(sql)
                    data = cursor.fetchall()
                    if len(data) == 0:
                        #该分类不存在
                        return
                    #3.从数据库中删除该分类
                    sql = f"delete from class where class_name = '{class_name}';"
                    cursor.execute(sql)
                    conn.commit()
                    return
    except Exception as e:
        print(f"删除分类失败：{e}")
        return JSONResponse(
            status_code = 400,
            content = {"message": "删除分类失败"}
        )

#处理发发布新闻的请求
def handle_release_news(in_data) -> JSONResponse:
    #1.提取数据
    title = in_data["title"]
    publisher = in_data["publisher"]
    _class = in_data["class"]
    body = in_data["body"]
    time = datetime.now().strftime("%Y年%m月%d日")
    id = str(uuid.uuid4())
    path = news_path + id + ".txt"
    if len(title) == 0 or len(publisher) == 0 or len(_class) == 0 or len(body) == 0:
        return JSONResponse(
            status_code = 400,
            content = {"message": "数据不完整"}
        )
    try:
        with pymysql.connect(**mysql_config) as conn:
                with conn.cursor() as cursor:
                    #2.将新闻信息添加到数据库中
                    sql = f"insert into news (title,publisher,class,time,id,path) values\
                          ('{title}','{publisher}','{_class}','{time}','{id}','{path}');"
                    cursor.execute(sql)
                    conn.commit()
        #3.将新闻的内容保存到文件中
        with open(static_path + news_path[1:] + id + ".txt","w") as file:
            file.write(body)
            return
    except Exception as e:
        print(f"发布新闻失败：{e}")
        return JSONResponse(
            status_code = 400,
            content = {"message": "发布新闻失败"}
        )

#处理获取一条新闻的请求
def handle_get_one_news(in_data) -> JSONResponse:
    #1.获取新闻的id
    news_id = in_data["news_id"]
    if len(news_id) <= 0:
        return JSONResponse(
            status_code = 400,
            content = {"message": "数据不完整"}
        )
    try:
        with pymysql.connect(**mysql_config) as conn:
           with conn.cursor() as cursor:
               #2.从数据库中获取这条新闻
               sql = f"select * from news where id = '{news_id}';"
               cursor.execute(sql)
               data = cursor.fetchall()
               return JSONResponse(
                   status_code=200,
                   content={
                       "title": data[0]["title"],
                       "publisher": data[0]["publisher"],
                       "class": data[0]["class"],
                       "path": data[0]["path"],
                       "time": data[0]["time"]
                   }
               )
    except Exception as e:
        print(f"获取一条新闻失败：{e}")
        return JSONResponse(
            status_code = 400,
            content = {"message": "获取一条新闻失败"}
        ) 
    
#处理删除一条新闻的请求
def handle_erase_news(in_data) -> JSONResponse:
    #1.获取新闻的id
    news_id = in_data["news_id"]
    if len(news_id) <= 0:
        return JSONResponse(
            status_code = 400,
            content = {"message": "数据不完整"}
        )
    try:
        with pymysql.connect(**mysql_config) as conn:
           with conn.cursor() as cursor:
               #2.从数据库中删除这条新闻
               sql = f"delete from news where id = '{news_id}';"
               cursor.execute(sql)
               conn.commit()
        #3.删除新闻对应的文件
        file_path = Path(static_path + news_path[1:] + news_id + ".txt")
        file_path.unlink()
    except Exception as e:
        print(f"删除一条新闻失败：{e}")
        return JSONResponse(
            status_code = 400,
            content = {"message": "删除一条新闻失败"}
        ) 
    

#处理编辑一条新闻的请求
def handle_modify_news(in_data) -> JSONResponse:
    #1.提取数据
    news_id = in_data["news_id"]
    title = in_data["title"]
    publisher = in_data["publisher"]
    _class = in_data["class"]
    body = in_data["body"]
    time = datetime.now().strftime("%Y年%m月%d日")
    path = news_path + news_id + ".txt"
    if len(title) == 0 or len(publisher) == 0 or len(_class) == 0 or len(body) == 0:
        return JSONResponse(
            status_code = 400,
            content = {"message": "数据不完整"}
        )
    try:
        with pymysql.connect(**mysql_config) as conn:
                with conn.cursor() as cursor:
                    #2.修改数据库中的新闻信息
                    sql = f"update news set title='{title}',publisher='{publisher}',class='{_class}',time='{time}' where id = '{news_id}';"
                    cursor.execute(sql)
                    conn.commit()
        #3.修改新闻文件中的内容
        with open(static_path + news_path[1:] + news_id + ".txt", "w") as file:
            file.write(body)
            return
    except Exception as e:
        print(f"发布新闻失败：{e}")
        return JSONResponse(
            status_code = 400,
            content = {"message": "发布新闻失败"}
        )

#这是整个服务器的入口
def main():
    app = FastAPI()

    #当服务器收到来自浏览器的请求时，回到下面去寻找对应的处理方法

    #1.收到注册请求
    @app.post("/register")
    async def register(req: Request):
        in_data = await req.json() #提取请求中的数据
        rsp = handle_register(in_data) #调用处理注册请求的方法
        return rsp

    #2.收到登录请求
    @app.post("/login")
    async def login(req: Request):
        in_data = await req.json() #提取请求中的数据
        rsp = handle_login(in_data) #调用处理登录请求的方法
        return rsp
    
    #3.收到获取所有新闻请求
    @app.get("/get_news")
    async def get_news():
        rsp = handle_get_news() #调用处理登录请求的方法
        return rsp
    
    #4.收到获取所有新闻类型请求
    @app.get("/get_class")
    async def get_class():
        rsp = handle_get_class() #调用处理登录请求的方法
        return rsp
    
    #5.收到新增分类请求
    @app.post("/add_class")
    async def add_class(req: Request):
        in_data = await req.json() #提取请求中的数据
        rsp = handle_add_class(in_data) #调用处理登录请求的方法
        return rsp
    
    #6.收到删除分类请求
    @app.post("/erase_class")
    async def erase_class(req: Request):
        in_data = await req.json() #提取请求中的数据
        rsp = handle_erase_class(in_data) #调用处理登录请求的方法
        return rsp
    
    #7.收到发布新闻请求
    @app.post("/release_news")
    async def release_news(req: Request):
        in_data = await req.json() #提取请求中的数据
        rsp = handle_release_news(in_data) #调用处理登录请求的方法
        return rsp
    
    #8.收到获取一条新闻请求
    @app.post("/get_one_news")
    async def get_one_news(req: Request):
        in_data = await req.json() #提取请求中的数据
        rsp = handle_get_one_news(in_data) #调用处理登录请求的方法
        return rsp
    
    #9.收到删除一条新闻请求
    @app.post("/erase_news")
    async def erase_news(req: Request):
        in_data = await req.json() #提取请求中的数据
        rsp = handle_erase_news(in_data) #调用处理登录请求的方法
        return rsp
    
    #10.收到获编辑一条新闻请求
    @app.post("/modify_news")
    async def modify_news(req: Request):
        in_data = await req.json() #提取请求中的数据
        rsp = handle_modify_news(in_data) #调用处理登录请求的方法
        return rsp
    
    #这是静态资源路径，当一个请求在上面无法找到对应的处理方法时，就会到里面去寻找资源
    app.mount("/", StaticFiles(directory="static",html=True), name="static")

    return app

if __name__ == "__main__":
    app = main()
    api_host = os.environ.get("API_HOST", "0.0.0.0")
    api_port = int(os.environ.get("API_PORT", "8080"))
    uvicorn.run(app, host=api_host, port=api_port, log_config={
            "version": 1,
            "formatters": {
                "default": {
                    "()": "uvicorn.logging.DefaultFormatter",
                    "fmt": "%(asctime)s - %(levelprefix)s %(message)s",
                    "datefmt": "%Y-%m-%d %H:%M:%S",
                },
            },
            "handlers": {
                "default": {
                    "formatter": "default",
                    "class": "logging.StreamHandler",
                    "stream": "ext://sys.stderr",
                },
            },
            "loggers": {
                "uvicorn": {"handlers": ["default"], "level": "INFO"},
            },
        })